Slide 1

Slide 1 text

KOTLIN GOES GLOBAL - By Karan Dhillon

Slide 2

Slide 2 text

Trip down the memory lane ● There has been numerous attempts on creating the best hybrid mobile framework in the past. ● Most notable candidates include React Native, Flutter, Xamarin, and Ionic. ● In order to understand why KMM is a gamechanger, we first have to briefly look into these past solutions.

Slide 3

Slide 3 text

● Biggest selling point is one codebase. This could also be extended to time saved on writing or managing one codebase. ● Fast - in shipping, in deploying, and in developing. ● Team size can be kept small, in return saving business expense. ● Slave to the framework. ● Starts fragmenting when TLOC gets bigger. The Good, The Bad, And The Ugly

Slide 4

Slide 4 text

Why is JetBrains jumping into the world of hybrid frameworks? The Question Remains Kotlin, a JVM based language, is very rapidly being adopted by the android community. Based on the demographic android covers, Kotlin would be an easy candidate for a hybrid framework.

Slide 5

Slide 5 text

“Kotlin Multiplatform Mobile (KMM) is an SDK for cross-platform mobile development provided by JetBrains. It uses the multiplatform capabilities of Kotlin and includes various tools and features designed to make the end-to-end experience of building mobile cross-platform applications as enjoyable and efficient as possible.”

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Expect/Actual Paradigm

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

//Common expect fun randomUUID(): String

Slide 10

Slide 10 text

//Common expect fun randomUUID(): String

Slide 11

Slide 11 text

//Common expect fun randomUUID(): String //Android import java.util.* actual fun randomUUID() = UUID.randomUUID().toString() //iOS import platform.Foundation.NSUUID actual fun randomUUID(): String = NSUUID().UUIDString()

Slide 12

Slide 12 text

//iOS import platform.Foundation.NSUUID actual fun randomUUID(): String = NSUUID().UUIDString() //Android import java.util.* actual fun randomUUID() = UUID.randomUUID().toString() //Common expect fun randomUUID(): String

Slide 13

Slide 13 text

Project Hierarchy

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

commonMain directory will hold the business logic that will be shared among all the platforms shared module will hold the shared business logic. This logic can be further dissected into platform specific directories androidMain & iosMain directories will hold the business logic that will be shared among the respective platform’s feature module

Slide 16

Slide 16 text

iOSTest directory will hold the business logic that can be reached by iOS module Compose-desktop module will hold the implementation logic for desktop support

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

KMM project requirements ● Android studio, version 4.2 or higher ● If iOS specific-code needs to be written, then Xcode version 11.3 or higher ● Compatible Kotlin compiler plugin ● Kotlin Multiplatform Mobile plugin in Android Studio ● JDK

Slide 19

Slide 19 text

Demo - SpaceX

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

commonMain directory will hold the business logic that will be shared among all the platforms shared module will hold the shared business logic. This logic can be further dissected into platform specific directories androidMain & iosMain directories will hold the business logic that will be shared among the respective platform’s feature module

Slide 23

Slide 23 text

plugins { kotlin("multiplatform") id("com.android.library") } kotlin { val coroutinesVersion = "1.3.9-native-mt" sourceSets { val commonMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") } } } } Build.gradle.kts (shared)

Slide 24

Slide 24 text

plugins { kotlin("multiplatform") id("com.android.library") } kotlin { val coroutinesVersion = "1.3.9-native-mt" sourceSets { val commonMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") } } } } Build.gradle.kts (shared)

Slide 25

Slide 25 text

buildscript { repositories { gradlePluginPortal() jcenter() google() mavenCentral() } dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10") classpath("com.android.tools.build:gradle:4.0.1") classpath("org.jetbrains.kotlin:kotlin-serialization:1.4.10") } } allprojects { repositories { google() jcenter() mavenCentral() } } Build.gradle.kts (root)

Slide 26

Slide 26 text

buildscript { repositories { gradlePluginPortal() jcenter() google() mavenCentral() } dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10") classpath("com.android.tools.build:gradle:4.0.1") classpath("org.jetbrains.kotlin:kotlin-serialization:1.4.10") } } allprojects { repositories { google() jcenter() mavenCentral() } } Build.gradle.kts (root)

Slide 27

Slide 27 text

plugins { kotlin("multiplatform") id("com.android.library") kotlin("plugin.serialization") } kotlin { val coroutinesVersion = "1.3.9-native-mt" val serializationVersion = "1.0.0-RC" sourceSets { val commonMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion") } } } } Build.gradle.kts (shared)

Slide 28

Slide 28 text

plugins { kotlin("multiplatform") id("com.android.library") kotlin("plugin.serialization") } kotlin { val coroutinesVersion = "1.3.9-native-mt" val serializationVersion = "1.0.0-RC" sourceSets { val commonMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion") } } } } Build.gradle.kts (shared)

Slide 29

Slide 29 text

Build.gradle.kts (shared) kotlin { val coroutinesVersion = "1.3.9-native-mt" val serializationVersion = "1.0.0-RC" val ktorVersion = "1.4.0" sourceSets { val commonMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion") implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-serialization:$ktorVersion") } } val androidMain by getting { dependencies { implementation("io.ktor:ktor-client-android:$ktorVersion") } } val iosMain by getting { dependencies { implementation("io.ktor:ktor-client-ios:$ktorVersion") } } } }

Slide 30

Slide 30 text

kotlin { val coroutinesVersion = "1.3.9-native-mt" val serializationVersion = "1.0.0-RC" val ktorVersion = "1.4.0" sourceSets { val commonMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion") implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-serialization:$ktorVersion") } } val androidMain by getting { dependencies { implementation("io.ktor:ktor-client-android:$ktorVersion") } } val iosMain by getting { dependencies { implementation("io.ktor:ktor-client-ios:$ktorVersion") } } } } Build.gradle.kts (shared)

Slide 31

Slide 31 text

kotlin.code.style=official xcodeproj=./iosApp android.useAndroidX=true kotlin.mpp.enableGranularSourceSetsMetadata=true kotlin.native.enableDependencyPropagation=false sqlDelightVersion=1.4.2 gradle.properties

Slide 32

Slide 32 text

kotlin.code.style=official xcodeproj=./iosApp android.useAndroidX=true kotlin.mpp.enableGranularSourceSetsMetadata=true kotlin.native.enableDependencyPropagation=false sqlDelightVersion=1.4.2 gradle.properties

Slide 33

Slide 33 text

buildscript { repositories { gradlePluginPortal() jcenter() google() mavenCentral() } val sqlDelightVersion: String by project dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10") classpath("com.android.tools.build:gradle:4.0.1") classpath("org.jetbrains.kotlin:kotlin-serialization:1.4.10") classpath("com.squareup.sqldelight:gradle-plugin:$sqlDelightVersion") } } allprojects { repositories { google() jcenter() mavenCentral() } } Build.gradle.kts (root)

Slide 34

Slide 34 text

buildscript { repositories { gradlePluginPortal() jcenter() google() mavenCentral() } val sqlDelightVersion: String by project dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10") classpath("com.android.tools.build:gradle:4.0.1") classpath("org.jetbrains.kotlin:kotlin-serialization:1.4.10") classpath("com.squareup.sqldelight:gradle-plugin:$sqlDelightVersion") } } allprojects { repositories { google() jcenter() mavenCentral() } } Build.gradle.kts (root)

Slide 35

Slide 35 text

Build.gradle.kts (shared) plugins { id("com.squareup.sqldelight") } kotlin { val sqlDelightVersion: String by project sourceSets { val commonMain by getting { dependencies { implementation("com.squareup.sqldelight:runtime:$sqlDelightVersion") } } val androidMain by getting { dependencies { implementation("com.squareup.sqldelight:android-driver:$sqlDelightVersion") } } val iosMain by getting { dependencies { implementation("com.squareup.sqldelight:native-driver:$sqlDelightVersion") } } } }

Slide 36

Slide 36 text

plugins { id("com.squareup.sqldelight") } kotlin { val sqlDelightVersion: String by project sourceSets { val commonMain by getting { dependencies { implementation("com.squareup.sqldelight:runtime:$sqlDelightVersion") } } val androidMain by getting { dependencies { implementation("com.squareup.sqldelight:android-driver:$sqlDelightVersion") } } val iosMain by getting { dependencies { implementation("com.squareup.sqldelight:native-driver:$sqlDelightVersion") } } } } Build.gradle.kts (shared)

Slide 37

Slide 37 text

Entity.kt(com.kdhillon.spacex.shared.entity) @Serializable data class RocketLaunch( @SerialName("flight_number") val flightNumber: Int, @SerialName("mission_name") val missionName: String, @SerialName("launch_year") val launchYear: Int, @SerialName("launch_date_utc") val launchDateUTC: String, @SerialName("rocket") val rocket: Rocket, @SerialName("details") val details: String?, @SerialName("launch_success") val launchSuccess: Boolean?, @SerialName("links") val links: Links ) @Serializable data class Rocket( @SerialName("rocket_id") val id: String, @SerialName("rocket_name") val name: String, @SerialName("rocket_type") val type: String ) @Serializable data class Links( @SerialName("mission_patch") val missionPatchUrl: String?, @SerialName("article_link") val articleUrl: String? )

Slide 38

Slide 38 text

@Serializable data class RocketLaunch( @SerialName("flight_number") val flightNumber: Int, @SerialName("mission_name") val missionName: String, @SerialName("launch_year") val launchYear: Int, @SerialName("launch_date_utc") val launchDateUTC: String, @SerialName("rocket") val rocket: Rocket, @SerialName("details") val details: String?, @SerialName("launch_success") val launchSuccess: Boolean?, @SerialName("links") val links: Links ) @Serializable data class Rocket( @SerialName("rocket_id") val id: String, @SerialName("rocket_name") val name: String, @SerialName("rocket_type") val type: String ) @Serializable data class Links( @SerialName("mission_patch") val missionPatchUrl: String?, @SerialName("article_link") val articleUrl: String? ) Entity.kt(com.kdhillon.spacex.shared.entity)

Slide 39

Slide 39 text

Build.gradle.kts (shared) plugins { // ... } kotlin { // ... } sqldelight { database("AppDatabase") { // The packageName parameter specifies the package name for the generated Kotlin sources. packageName = "com.kdhillon.spacex.shared.cache" } }

Slide 40

Slide 40 text

plugins { // ... } kotlin { // ... } sqldelight { database("AppDatabase") { // The packageName parameter specifies the package name for the generated Kotlin sources. packageName = "com.kdhillon.spacex.shared.cache" } } Build.gradle.kts (shared)

Slide 41

Slide 41 text

First we need to create .sq file, which will contain all the SQL queries. By default, SQLDelight plugin reads .sq from the sqldelight folder. So create the package specified in the packageName parameter.

Slide 42

Slide 42 text

Inside our package, we need to create a .sq file with the name of our database (in our case, AppDatabase.sq)

Slide 43

Slide 43 text

AppDatabase.sq CREATE TABLE Launch ( flightNumber INTEGER NOT NULL, missionName TEXT NOT NULL, launchYear INTEGER AS Int NOT NULL DEFAULT 0, rocketId TEXT NOT NULL, details TEXT, launchSuccess INTEGER AS Boolean DEFAULT NULL, launchDateUTC TEXT NOT NULL, missionPatchUrl TEXT, articleUrl TEXT ); CREATE TABLE Rocket ( id TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL, type TEXT NOT NULL );

Slide 44

Slide 44 text

CREATE TABLE Launch ( flightNumber INTEGER NOT NULL, missionName TEXT NOT NULL, launchYear INTEGER AS Int NOT NULL DEFAULT 0, rocketId TEXT NOT NULL, details TEXT, launchSuccess INTEGER AS Boolean DEFAULT NULL, launchDateUTC TEXT NOT NULL, missionPatchUrl TEXT, articleUrl TEXT ); CREATE TABLE Rocket ( id TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL, type TEXT NOT NULL ); AppDatabase.sq

Slide 45

Slide 45 text

AppDatabase.sq CREATE TABLE Launch ( flightNumber INTEGER NOT NULL, missionName TEXT NOT NULL, launchYear INTEGER AS Int NOT NULL DEFAULT 0, rocketId TEXT NOT NULL, details TEXT, launchSuccess INTEGER AS Boolean DEFAULT NULL, launchDateUTC TEXT NOT NULL, missionPatchUrl TEXT, articleUrl TEXT ); CREATE TABLE Rocket ( id TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL, type TEXT NOT NULL );

Slide 46

Slide 46 text

insertLaunch: INSERT INTO Launch(flightNumber, missionName, launchYear, rocketId, details, launchSuccess, launchDateUTC, missionPatchUrl, articleUrl) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?); insertRocket: INSERT INTO Rocket(id, name, type) VALUES(?, ?, ?); AppDatabase.sq

Slide 47

Slide 47 text

insertLaunch: INSERT INTO Launch(flightNumber, missionName, launchYear, rocketId, details, launchSuccess, launchDateUTC, missionPatchUrl, articleUrl) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?); insertRocket: INSERT INTO Rocket(id, name, type) VALUES(?, ?, ?); AppDatabase.sq

Slide 48

Slide 48 text

insertLaunch: INSERT INTO Launch(flightNumber, missionName, launchYear, rocketId, details, launchSuccess, launchDateUTC, missionPatchUrl, articleUrl) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?); insertRocket: INSERT INTO Rocket(id, name, type) VALUES(?, ?, ?); AppDatabase.sq

Slide 49

Slide 49 text

removeAllLaunches: DELETE FROM Launch; removeAllRockets: DELETE FROM Rocket; AppDatabase.sq

Slide 50

Slide 50 text

removeAllLaunches: DELETE FROM Launch; removeAllRockets: DELETE FROM Rocket; AppDatabase.sq

Slide 51

Slide 51 text

removeAllLaunches: DELETE FROM Launch; removeAllRockets: DELETE FROM Rocket; AppDatabase.sq

Slide 52

Slide 52 text

selectRocketById: SELECT * FROM Rocket WHERE id = ?; selectAllLaunchesInfo: SELECT Launch.*, Rocket.* FROM Launch LEFT JOIN Rocket ON Rocket.id == Launch.rocketId; AppDatabase.sq

Slide 53

Slide 53 text

selectRocketById: SELECT * FROM Rocket WHERE id = ?; selectAllLaunchesInfo: SELECT Launch.*, Rocket.* FROM Launch LEFT JOIN Rocket ON Rocket.id == Launch.rocketId; AppDatabase.sq

Slide 54

Slide 54 text

selectRocketById: SELECT * FROM Rocket WHERE id = ?; selectAllLaunchesInfo: SELECT Launch.*, Rocket.* FROM Launch LEFT JOIN Rocket ON Rocket.id == Launch.rocketId; AppDatabase.sq

Slide 55

Slide 55 text

When the project is compiled, the generated kotlin code will be stored in our packageName directory.

Slide 56

Slide 56 text

interface AppDatabase : Transacter { val appDatabaseQueries: AppDatabaseQueries companion object { val Schema: SqlDriver.Schema get() = AppDatabase::class.schema operator fun invoke(driver: SqlDriver): AppDatabase = AppDatabase::class.newInstance(driver)} } AppDatabase.kt(com.kdhillon.spacex.shared.cache)

Slide 57

Slide 57 text

interface AppDatabase : Transacter { val appDatabaseQueries: AppDatabaseQueries companion object { val Schema: SqlDriver.Schema get() = AppDatabase::class.schema operator fun invoke(driver: SqlDriver): AppDatabase = AppDatabase::class.newInstance(driver)} } AppDatabase.kt(com.kdhillon.spacex.shared.cache)

Slide 58

Slide 58 text

interface AppDatabase : Transacter { val appDatabaseQueries: AppDatabaseQueries companion object { val Schema: SqlDriver.Schema get() = AppDatabase::class.schema operator fun invoke(driver: SqlDriver): AppDatabase = AppDatabase::class.newInstance(driver)} } AppDatabase.kt(com.kdhillon.spacex.shared.cache)

Slide 59

Slide 59 text

interface AppDatabaseQueries : Transacter { fun selectRocketById(id: String, mapper: ( id: String, name: String, type: String ) -> T): Query fun selectRocketById(id: String): Query fun selectAllLaunchesInfo(mapper: ( flightNumber: Long, missionName: String, launchYear: Int, rocketId: String, details: String?, launchSuccess: Boolean?, launchDateUTC: String, missionPatchUrl: String?, articleUrl: String?, id: String?, name: String?, type: String? ) -> T): Query fun selectAllLaunchesInfo(): Query ... AppDatabaseQueries.kt(com.kdhillon.spacex.shared.cache)

Slide 60

Slide 60 text

interface AppDatabaseQueries : Transacter { fun selectRocketById(id: String, mapper: ( id: String, name: String, type: String ) -> T): Query fun selectRocketById(id: String): Query fun selectAllLaunchesInfo(mapper: ( flightNumber: Long, missionName: String, launchYear: Int, rocketId: String, details: String?, launchSuccess: Boolean?, launchDateUTC: String, missionPatchUrl: String?, articleUrl: String?, id: String?, name: String?, type: String? ) -> T): Query fun selectAllLaunchesInfo(): Query ... AppDatabaseQueries.kt(com.kdhillon.spacex.shared.cache)

Slide 61

Slide 61 text

interface AppDatabaseQueries : Transacter { fun selectRocketById(id: String, mapper: ( id: String, name: String, type: String ) -> T): Query fun selectRocketById(id: String): Query fun selectAllLaunchesInfo(mapper: ( flightNumber: Long, missionName: String, launchYear: Int, rocketId: String, details: String?, launchSuccess: Boolean?, launchDateUTC: String, missionPatchUrl: String?, articleUrl: String?, id: String?, name: String?, type: String? ) -> T): Query fun selectAllLaunchesInfo(): Query ... AppDatabaseQueries.kt(com.kdhillon.spacex.shared.cache)

Slide 62

Slide 62 text

interface AppDatabase : Transacter { val appDatabaseQueries: AppDatabaseQueries companion object { val Schema: SqlDriver.Schema get() = AppDatabase::class.schema operator fun invoke(driver: SqlDriver): AppDatabase = AppDatabase::class.newInstance(driver)} } AppDatabase.kt(com.kdhillon.spacex.shared.cache)

Slide 63

Slide 63 text

interface AppDatabase : Transacter { val appDatabaseQueries: AppDatabaseQueries companion object { val Schema: SqlDriver.Schema get() = AppDatabase::class.schema operator fun invoke(driver: SqlDriver): AppDatabase = AppDatabase::class.newInstance(driver)} } AppDatabase.kt(com.kdhillon.spacex.shared.cache)

Slide 64

Slide 64 text

expect class DatabaseDriverFactory { fun createDriver(): SqlDriver } DatabaseDriverFactory.kt(com.kdhillon.spacex.shared.cache)

Slide 65

Slide 65 text

actual class DatabaseDriverFactory(private val context: Context) { actual fun createDriver(): SqlDriver { return AndroidSqliteDriver(AppDatabase.Schema, context, "test.db") } } DatabaseDriverFactory.kt(com.kdhillon.spacex.shared.cache in androidMain)

Slide 66

Slide 66 text

actual class DatabaseDriverFactory(private val context: Context) { actual fun createDriver(): SqlDriver { return AndroidSqliteDriver(AppDatabase.Schema, context, "test.db") } } DatabaseDriverFactory.kt(com.kdhillon.spacex.shared.cache in androidMain)

Slide 67

Slide 67 text

actual class DatabaseDriverFactory { actual fun createDriver(): SqlDriver { return NativeSqliteDriver(AppDatabase.Schema, "test.db") } } DatabaseDriverFactory.kt(com.kdhillon.spacex.shared.cache in iosMain)

Slide 68

Slide 68 text

actual class DatabaseDriverFactory { actual fun createDriver(): SqlDriver { return NativeSqliteDriver(AppDatabase.Schema, "test.db") } } DatabaseDriverFactory.kt(com.kdhillon.spacex.shared.cache in iosMain)

Slide 69

Slide 69 text

actual class DatabaseDriverFactory { actual fun createDriver(): SqlDriver { return NativeSqliteDriver(AppDatabase.Schema, "test.db") } } DatabaseDriverFactory.kt(com.kdhillon.spacex.shared.cache in iosMain)

Slide 70

Slide 70 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache)

Slide 71

Slide 71 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache)

Slide 72

Slide 72 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache) expect class DatabaseDriverFactory { fun createDriver(): SqlDriver }

Slide 73

Slide 73 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache)

Slide 74

Slide 74 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache)

Slide 75

Slide 75 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache) actual class DatabaseDriverFactory(private val context: Context) { actual fun createDriver(): SqlDriver { return AndroidSqliteDriver(AppDatabase.Schema, context, "test.db") } }

Slide 76

Slide 76 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache) actual class DatabaseDriverFactory(private val context: Context) { actual fun createDriver(): SqlDriver { return AndroidSqliteDriver(AppDatabase.Schema, context, "test.db") } }

Slide 77

Slide 77 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache) actual class DatabaseDriverFactory(private val context: Context) { actual fun createDriver(): SqlDriver { return AndroidSqliteDriver(AppDatabase.Schema, context, "test.db") } } actual class DatabaseDriverFactory { actual fun createDriver(): SqlDriver { return NativeSqliteDriver(AppDatabase.Schema, "test.db") } }

Slide 78

Slide 78 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache) actual class DatabaseDriverFactory(private val context: Context) { actual fun createDriver(): SqlDriver { return AndroidSqliteDriver(AppDatabase.Schema, context, "test.db") } } actual class DatabaseDriverFactory { actual fun createDriver(): SqlDriver { return NativeSqliteDriver(AppDatabase.Schema, "test.db") } }

Slide 79

Slide 79 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache) actual class DatabaseDriverFactory(private val context: Context) { actual fun createDriver(): SqlDriver { return AndroidSqliteDriver(AppDatabase.Schema, context, "test.db") } } actual class DatabaseDriverFactory { actual fun createDriver(): SqlDriver { return NativeSqliteDriver(AppDatabase.Schema, "test.db") } }

Slide 80

Slide 80 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache)

Slide 81

Slide 81 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache)

Slide 82

Slide 82 text

internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries } Database.kt(com.kdhillon.spacex.shared.cache)

Slide 83

Slide 83 text

class SpaceXApi { companion object { private const val LAUNCHES_ENDPOINT = "https://api.spacexdata.com/v3/launches" } private val httpClient = HttpClient { install(JsonFeature) { val json = kotlinx.serialization.json.Json { ignoreUnknownKeys = true } serializer = KotlinxSerializer(json) } } suspend fun getAllLaunches(): List { return httpClient.get(LAUNCHES_ENDPOINT) } } SpaceXApi.kt(com.kdhillon.spacex.shared.network)

Slide 84

Slide 84 text

class SpaceXApi { companion object { private const val LAUNCHES_ENDPOINT = "https://api.spacexdata.com/v3/launches" } private val httpClient = HttpClient { install(JsonFeature) { val json = kotlinx.serialization.json.Json { ignoreUnknownKeys = true } serializer = KotlinxSerializer(json) } } suspend fun getAllLaunches(): List { return httpClient.get(LAUNCHES_ENDPOINT) } } SpaceXApi.kt(com.kdhillon.spacex.shared.network)

Slide 85

Slide 85 text

class SpaceXApi { companion object { private const val LAUNCHES_ENDPOINT = "https://api.spacexdata.com/v3/launches" } private val httpClient = HttpClient { install(JsonFeature) { val json = kotlinx.serialization.json.Json { ignoreUnknownKeys = true } serializer = KotlinxSerializer(json) } } suspend fun getAllLaunches(): List { return httpClient.get(LAUNCHES_ENDPOINT) } } SpaceXApi.kt(com.kdhillon.spacex.shared.network)

Slide 86

Slide 86 text

AndroidManifest.xml(/Users/karandhillon/Desktop/Personal/SpaceX/androidApp/src/main/AndroidManifest.xml)

Slide 87

Slide 87 text

AndroidManifest.xml(/Users/karandhillon/Desktop/Personal/SpaceX/androidApp/src/main/AndroidManifest.xml)

Slide 88

Slide 88 text

AndroidManifest.xml(/Users/karandhillon/Desktop/Personal/SpaceX/androidApp/src/main/AndroidManifest.xml)

Slide 89

Slide 89 text

class SpaceXSDK(databaseDriverFactory: DatabaseDriverFactory) { private val database = Database(databaseDriverFactory) private val api = SpaceXApi() @Throws(Exception::class) suspend fun getLaunches(forceReload: Boolean): List { val cachedLaunches = database.getAllLaunches() return if (cachedLaunches.isNotEmpty() && !forceReload) { cachedLaunches } else { api.getAllLaunches().also { database.clearDatabase() database.createLaunches(it) } } } } SpaceXSDK.kt(com.kdhillon.spacex.shared)

Slide 90

Slide 90 text

plugins { id("com.android.application") kotlin("android") } dependencies { implementation(project(":shared")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") implementation("androidx.core:core-ktx:1.3.1") implementation("com.google.android.material:material:1.2.0") implementation("androidx.appcompat:appcompat:1.2.0") implementation("androidx.constraintlayout:constraintlayout:2.0.0") implementation("androidx.recyclerview:recyclerview:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.cardview:cardview:1.0.0") } Build.gradle.kts (androidApp)

Slide 91

Slide 91 text

plugins { id("com.android.application") kotlin("android") } dependencies { implementation(project(":shared")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") implementation("androidx.core:core-ktx:1.3.1") implementation("com.google.android.material:material:1.2.0") implementation("androidx.appcompat:appcompat:1.2.0") implementation("androidx.constraintlayout:constraintlayout:2.0.0") implementation("androidx.recyclerview:recyclerview:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.cardview:cardview:1.0.0") } Build.gradle.kts (androidApp)

Slide 92

Slide 92 text

plugins { id("com.android.application") kotlin("android") } dependencies { implementation(project(":shared")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") implementation("androidx.core:core-ktx:1.3.1") implementation("com.google.android.material:material:1.2.0") implementation("androidx.appcompat:appcompat:1.2.0") implementation("androidx.constraintlayout:constraintlayout:2.0.0") implementation("androidx.recyclerview:recyclerview:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.cardview:cardview:1.0.0") } Build.gradle.kts (androidApp)

Slide 93

Slide 93 text

class MainActivity : AppCompatActivity() { private val mainScope = MainScope() private val sdk = SpaceXSDK(DatabaseDriverFactory(this)) private lateinit var launchesRecyclerView: RecyclerView private lateinit var progressBarView: FrameLayout private lateinit var swipeRefreshLayout: SwipeRefreshLayout private val launchesRvAdapter = LaunchesRvAdapter(listOf()) override fun onCreate(savedInstanceState: Bundle?) { // ... } private fun displayLaunches(needReload: Boolean) { progressBarView.isVisible = true mainScope.launch { kotlin.runCatching { sdk.getLaunches(needReload) }.onSuccess { launchesRvAdapter.launches = it launchesRvAdapter.notifyDataSetChanged() }.onFailure { Toast.makeText(this@MainActivity, it.localizedMessage, Toast.LENGTH_SHORT).show() } progressBarView.isVisible = false } } } MainActivity.kt (androidApp)

Slide 94

Slide 94 text

class MainActivity : AppCompatActivity() { private val mainScope = MainScope() private val sdk = SpaceXSDK(DatabaseDriverFactory(this)) private lateinit var launchesRecyclerView: RecyclerView private lateinit var progressBarView: FrameLayout private lateinit var swipeRefreshLayout: SwipeRefreshLayout private val launchesRvAdapter = LaunchesRvAdapter(listOf()) override fun onCreate(savedInstanceState: Bundle?) { // ... } private fun displayLaunches(needReload: Boolean) { progressBarView.isVisible = true mainScope.launch { kotlin.runCatching { sdk.getLaunches(needReload) }.onSuccess { launchesRvAdapter.launches = it launchesRvAdapter.notifyDataSetChanged() }.onFailure { Toast.makeText(this@MainActivity, it.localizedMessage, Toast.LENGTH_SHORT).show() } progressBarView.isVisible = false } } } MainActivity.kt (androidApp)

Slide 95

Slide 95 text

class MainActivity : AppCompatActivity() { private val mainScope = MainScope() private val sdk = SpaceXSDK(DatabaseDriverFactory(this)) private lateinit var launchesRecyclerView: RecyclerView private lateinit var progressBarView: FrameLayout private lateinit var swipeRefreshLayout: SwipeRefreshLayout private val launchesRvAdapter = LaunchesRvAdapter(listOf()) override fun onCreate(savedInstanceState: Bundle?) { // ... } private fun displayLaunches(needReload: Boolean) { progressBarView.isVisible = true mainScope.launch { kotlin.runCatching { sdk.getLaunches(needReload) }.onSuccess { launchesRvAdapter.launches = it launchesRvAdapter.notifyDataSetChanged() }.onFailure { Toast.makeText(this@MainActivity, it.localizedMessage, Toast.LENGTH_SHORT).show() } progressBarView.isVisible = false } } } MainActivity.kt (androidApp)

Slide 96

Slide 96 text

class MainActivity : AppCompatActivity() { private val mainScope = MainScope() private val sdk = SpaceXSDK(DatabaseDriverFactory(this)) private lateinit var launchesRecyclerView: RecyclerView private lateinit var progressBarView: FrameLayout private lateinit var swipeRefreshLayout: SwipeRefreshLayout private val launchesRvAdapter = LaunchesRvAdapter(listOf()) override fun onCreate(savedInstanceState: Bundle?) { // ... } private fun displayLaunches(needReload: Boolean) { progressBarView.isVisible = true mainScope.launch { kotlin.runCatching { sdk.getLaunches(needReload) }.onSuccess { launchesRvAdapter.launches = it launchesRvAdapter.notifyDataSetChanged() }.onFailure { Toast.makeText(this@MainActivity, it.localizedMessage, Toast.LENGTH_SHORT).show() } progressBarView.isVisible = false } } } MainActivity.kt (androidApp)

Slide 97

Slide 97 text

class MainActivity : AppCompatActivity() { private val mainScope = MainScope() private val sdk = SpaceXSDK(DatabaseDriverFactory(this)) private lateinit var launchesRecyclerView: RecyclerView private lateinit var progressBarView: FrameLayout private lateinit var swipeRefreshLayout: SwipeRefreshLayout private val launchesRvAdapter = LaunchesRvAdapter(listOf()) override fun onCreate(savedInstanceState: Bundle?) { // ... } private fun displayLaunches(needReload: Boolean) { progressBarView.isVisible = true mainScope.launch { kotlin.runCatching { sdk.getLaunches(needReload) }.onSuccess { launchesRvAdapter.launches = it launchesRvAdapter.notifyDataSetChanged() }.onFailure { Toast.makeText(this@MainActivity, it.localizedMessage, Toast.LENGTH_SHORT).show() } progressBarView.isVisible = false } } } MainActivity.kt (androidApp)

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

Challenges Ahead With KMM ● KMM is still in alpha, and the public API’s are expected to be changed frequently given its scale. ● Deploying a KMM project in production therefore should be prohibited. ● A lot of edge-cases might be encountered when working in KMM, such as how to manage sensitive information. ● iOS counterpart receive the shared module as a framework dependency, where the class files are in Objective-C format. Even though using appCode IDE helps a bit in navigating these Obj-C files, having them in Swift would be the ideal case.

Slide 100

Slide 100 text

Is KMM The 🔑 To Hybrid?

Slide 101

Slide 101 text

Resources KMM Documentation: https://kotlinlang.org/docs/mobile/home.html KMM Video Tutorials: https://youtube.com/playlist?list=PLlFc5cFwUnmy _oVc9YQzjasSNoAk4hk_C Reach out to me@ Twitter: @karandhillon95 Blog: https://kdhillon.com