$30 off During Our Annual Pro Sale. View Details »

Kotlin Multiplatform

Kotlin Multiplatform

Loveleen Kaur

June 30, 2023
Tweet

More Decks by Loveleen Kaur

Other Decks in Programming

Transcript

  1. Meet Kotlin
    Multiplatform Mobile
    KMM for short
    Loveleen Kaur
    Senior Engineer (Astrotalk)
    Google Android Educator

    View Slide

  2. Who am I?
    ● Loveleen Kaur
    ● Senior Engineer - Android @ Astrotalk
    ● Core Team Member @ Google Developer Groups Chandigarh
    ● Co-Organizer @ Kotlin Chandigarh User Group
    ● Android Educator @ Android Educators Community India
    ● Phd Research Scholar
    ● Technical Speaker
    ● Develop Mobile Applications
    ● Happy Android Developer :)

    View Slide

  3. Questions I will try to answer
    -What is the difference between KMM and other multiplatform
    solutions like a flutter, react native, etc?
    -Is this a good solution for your business and why we thought it is
    a good decision for us?
    -How does iOS read the shared module?
    -How much code can we actually share?
    -What are the most important libraries and what these are doing?
    -Is KMM production ready?
    -Pros and cons of KMM

    View Slide

  4. Cross-platform mobile framework popularity

    View Slide

  5. Cross-platform mobile framework popularity

    View Slide

  6. Google trends

    View Slide

  7. Share code
    jvm
    Android
    iOS
    Linux
    macOS
    Windows
    JS
    watchOS
    tvOS

    View Slide

  8. KMM
    First Multiplatform
    Kotlin 1.2 November 2017
    Announced August 2020

    View Slide

  9. Why we choose KMM
    • Small team
    • Business logic in one place
    • iOS needed refactor

    View Slide

  10. KMM, the PoC
    • Configuration is tricky, but manageable
    • Things that works on android, do not need to
    work on iOS
    • One repo vs 3 repos
    • iOS simulator != iOS real device
    • Sample project != big project

    View Slide

  11. Networking October 2021

    View Slide

  12. January 2022
    Networking
    Persistance
    Models

    View Slide

  13. March 2022
    Networking
    Persistance
    Models
    Repositories
    UseCases

    View Slide

  14. June/July 2022
    Networking
    Persistance
    Models
    Repositories
    UseCases
    View Models
    UI layer

    View Slide

  15. Share
    code
    Domain
    Repositories
    Persistance Networking
    ViewModels
    Presentation
    Business(Kotlin)
    Framework(iOS,
    Android)

    View Slide

  16. KMM libraries
    137 libraries
    KTOR
    KOIN
    SQLdelight
    Multiplatform settings
    kotlinx.serialization
    kotlinx.datetime
    BuildKonfig
    Moko-resources

    View Slide

  17. Networking with KTOR
    • open-source
    • lightweight
    • powered by coroutines
    • Build by JetBrains

    View Slide

  18. Networking with KTOR
    fun createHttpClient(httpClientEngine: HttpClientEngine, logger: Logger) =
    HttpClient(httpClientEngine) {
    defaultRequest {
    url.protocol = URLProtocol.HTTPS url.host =
    BuildKonfig.BASE_URL parameter("apikey",
    BuildKonfig.API_KEY)
    parameter("Accept-Language", languageCode ?: "en")
    }
    install(ContentNegotiation) { json(json) }
    install(Logging) { this.logger =
    logger level = logLevel
    }
    }

    View Slide

  19. Networking with KTOR
    fun createHttpClient(httpClientEngine: HttpClientEngine,
    logger: Logger) =
    HttpClient(httpClientEngine) {
    defaultRequest {
    url.protocol = URLProtocol.HTTPS url.host =
    BuildKonfig.BASE_URL parameter("apikey",
    BuildKonfig.API_KEY)
    parameter("Accept-Language", languageCode ?: "en")
    }
    install(ContentNegotiation) { json(json) }
    install(Logging) {
    this.logger = logger level
    = logLevel
    }
    }
    OkHttp + Retrofit
    Retrofit.Builder().baseUrl()
    In interceptor:
    chain.request().header()

    View Slide

  20. fun createHttpClient(httpClientEngine: HttpClientEngine, logger: Logger) =
    HttpClient(httpClientEngine) {
    defaultRequest {
    url.protocol = URLProtocol.HTTPS
    url.host = BuildKonfig.BASE_URL
    parameter("apikey", BuildKonfig.API_KEY)
    parameter("Accept-Language", languageCode ?: "en")
    }
    install(ContentNegotiation) { json(json) }
    install(Logging) {
    this.logger = logger
    level = logLevel
    }
    }
    OkHttp + Retrofit
    Retrofit.Builder().baseUrl(
    )
    In interceptor:
    chain.request().header(
    )
    .addConverterFactory(jsonConverter)
    Networking with KTOR

    View Slide

  21. fun createHttpClient(httpClientEngine: HttpClientEngine, logger: Logger) =
    HttpClient(httpClientEngine) {
    defaultRequest {
    url.protocol = URLProtocol.HTTPS
    url.host = BuildKonfig.BASE_URL
    parameter("apikey", BuildKonfig.API_KEY)
    parameter("Accept-Language", languageCode ?: "en")
    }
    install(ContentNegotiation) { json(json) }
    install(Logging) {
    this.logger = logger
    level = logLevel
    }
    }
    OkHttp + Retrofit
    Retrofit.Builder().baseUrl(
    )
    In interceptor:
    chain.request().header(
    )
    .addConverterFactory(jsonConverter)
    .addInterceptor(httpLoggingInterceptor)
    Networking with KTOR

    View Slide

  22. Http Client Engine Android.create()
    val logger = object : Logger {
    override fun log(message: String) {
    Log.i(tag, message)
    }
    }
    Darwin.create()
    Logger.DEFAULT
    Logger
    Networking with KTOR

    View Slide

  23. Actual/Expected

    View Slide

  24. Actual/Expected
    //common
    expect val httpEngine: HttpClientEngine expect val
    logger: Logger
    //androidMain
    actual val httpEngine = Android.create() actual val
    logger = object : Logger {
    override fun log(message: String) { Log.i(tag,
    message)
    }
    }
    //iosMain
    actual val httpEngine = Darwin.create() actual val
    logger = Logger.DEFAULT

    View Slide

  25. iOS and
    coroutines

    View Slide

  26. iOS and
    coroutines
    Async/Await
    RxSwift
    Combine
    Callbacks

    View Slide

  27. iOS
    Source: https://dev.to/touchlab/kotlin-coroutines-and-swift-revisited-j5h
    func createPublisher(flowAdapter: FlowAdapter) -> AnyPublisher {
    return Deferred>> {
    let subject = PassthroughSubject()
    let job = flowAdapter.subscribe(
    onEvent: { (item) in let _ = subject.send(item) },
    onError: { (error) in subject.send(completion: .failure(KotlinError(error))) },
    onComplete: { subject.send(completion: .finished) }
    )
    return subject.handleEvents(receiveCancel: {
    job.cancel(cause: nil)
    })
    }.eraseToAnyPublisher()
    }

    View Slide

  28. Shared
    iOS
    class FlowAdapter(
    private val scope: CoroutineScope,
    private val flow: Flow
    ) {
    fun subscribe(
    onEvent: (T) -> Unit,
    onError: (Throwable) -> Unit,
    onComplete: () -> Unit
    ): Job =
    flow
    .onEach { onEvent(it) }
    .catch { onError(it) }
    .onCompletion { onComplete() }
    .launchIn(scope)
    }

    View Slide

  29. SSOT for
    resources

    View Slide

  30. Resources
    Moko-resources
    https://github.com/icerockdev/moko-resources

    View Slide

  31. Strings.xml
    Resources
    Cancel
    Current location
    Yesterday

    View Slide

  32. Compose
    Resources
    stringResource(id = SharedRes.strings.my_string.resourceId)
    Swift/SwiftUI
    let string = MR.strings().my_string.desc().localized()
    LocalizedStringKey(MR.strings().email.resourceId)

    View Slide

  33. Resources
    Common
    val stringDesc = StringDesc.Resource(SharedRes.strings.airlypedia_title)

    View Slide

  34. Resources
    Common
    val stringDesc = StringDesc.Resource(SharedRes.strings.airlypedia_title)
    .toString(context)

    View Slide

  35. Resources
    Common
    val stringDesc = StringDesc.Resource(SharedRes.strings.airlypedia_title)
    .toString(context) .localized()

    View Slide

  36. Resources
    Common
    val stringDesc = StringDesc.Resource(SharedRes.strings.airlypedia_title)
    expect fun ResourceStringDesc.getText(): String

    View Slide

  37. Resources
    Common
    val stringDesc = StringDesc.Resource(SharedRes.strings.airlypedia_title)
    expect fun ResourceStringDesc.getText(): String
    actual fun ResourceStringDesc.getText(): String =
    this.toString(appContext)

    View Slide

  38. Resources
    Common
    val stringDesc = StringDesc.Resource(SharedRes.strings.airlypedia_title)
    expect fun ResourceStringDesc.getText(): String
    actual fun ResourceStringDesc.getText(): String =
    this.toString(appContext)
    actual fun ResourceStringDesc.getText(): String =
    this.localized()

    View Slide

  39. How to read KMM in platforms?

    View Slide

  40. View Slide

  41. dependencies {
    implementation(project(":shared"))
    }

    View Slide

  42. dependencies {
    implementation(project(":shared"))
    }

    View Slide

  43. dependencies {
    implementation(project(":shared"))
    }
    New Run Script Phase

    View Slide

  44. dependencies {
    implementation(project(":shared"))
    }
    New Run Script Phase
    https://kotlinlang.org/docs

    View Slide

  45. Next steps
    • Use async/await in iOS for suspend functions
    • Try crashlytics, analytics, etc
    • Use KMM for web app
    • Share more assets

    View Slide

  46. iOS & Android everyday

    View Slide

  47. iOS & Android using KMM

    View Slide

  48. Is it ready for production?

    View Slide

  49. KMM - Pros and
    Cons
    • 100% Native
    • Android developers know
    Kotlin and Gradle
    • Optional
    • Still in Alpha

    View Slide

  50. KMM - Pros and
    Cons
    • 100% Native
    • Android developers know
    Kotlin and Gradle
    • Optional
    • Still in Alpha
    • UI written separately

    View Slide

  51. Meet Kotlin
    Multiplatform Mobile
    Thanks for listening!
    Loveleen Kaur
    Senior Engineer (Astrotalk)
    Google Android Educator

    View Slide