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

Meet Kotlin Multiplatform Mobile

Meet Kotlin Multiplatform Mobile

Presentation from Kotlin Dev Day Amsterdam 2022

Piotr Prus

May 23, 2022
Tweet

More Decks by Piotr Prus

Other Decks in Programming

Transcript

  1. Questions I will try to answer - What is the

    difference between KMM and other multiplatform solutions like a fl utter, 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
  2. Why we choose KMM • Small team • Business logic

    in one place • Promote DDD • iOS needed refactor
  3. KMM, the PoC • Con fi guration 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 1 week 🎉
  4. Networking with KTOR fun createHttpClient(httpClientEngine: HttpClientEngine, logger: Logger) = HttpClient(httpClientEngine)

    { defaultRequest { url.protocol = URLProtocol.HTTP S url.host = BuildKonfig.BASE_UR L parameter("apikey", BuildKonfig.API_KEY ) parameter("Accept-Language", languageCode ?: "en" ) } install(ContentNegotiation) { json(json) } install(Logging) { this.logger = logge r level = logLeve l } }
  5. Networking with KTOR fun createHttpClient(httpClientEngine: HttpClientEngine, logger: Logger) = HttpClient(httpClientEngine)

    { defaultRequest { url.protocol = URLProtocol.HTTP S url.host = BuildKonfig.BASE_UR L parameter("apikey", BuildKonfig.API_KEY ) parameter("Accept-Language", languageCode ?: "en" ) } install(ContentNegotiation) { json(json) } install(Logging) { this.logger = logge r level = logLeve l } } OkHttp + Retro fi t Retro fi t.Builder().baseUrl() In interceptor: chain.request().header()
  6. Networking with KTOR fun createHttpClient(httpClientEngine: HttpClientEngine, logger: Logger) = HttpClient(httpClientEngine)

    { defaultRequest { url.protocol = URLProtocol.HTTP S url.host = BuildKonfig.BASE_UR L parameter("apikey", BuildKonfig.API_KEY ) parameter("Accept-Language", languageCode ?: "en" ) } install(ContentNegotiation) { json(json) } install(Logging) { this.logger = logge r level = logLeve l } } OkHttp + Retro fi t Retro fi t.Builder().baseUrl() In interceptor: chain.request().header() .addConverterFactory(jsonConverter)
  7. Networking with KTOR fun createHttpClient(httpClientEngine: HttpClientEngine, logger: Logger) = HttpClient(httpClientEngine)

    { defaultRequest { url.protocol = URLProtocol.HTTP S url.host = BuildKonfig.BASE_UR L parameter("apikey", BuildKonfig.API_KEY ) parameter("Accept-Language", languageCode ?: "en" ) } install(ContentNegotiation) { json(json) } install(Logging) { this.logger = logge r level = logLeve l } } OkHttp + Retro fi t Retro fi t.Builder().baseUrl() In interceptor: chain.request().header() .addConverterFactory(jsonConverter) .addInterceptor(httpLoggingInterceptor)
  8. Http Client Engine Networking with KTOR Android.create() val logger =

    object : Logger { override fun log(message: String) { Log.i(tag, message) } } Darwin.create() Logger.DEFAULT Logger
  9. Actual/Expected //commo n expect val httpEngine: HttpClientEngin e expect val

    logger: Logger //androidMai n actual val httpEngine = Android.create( ) actual val logger = object : Logger { override fun log(message: String) { Log.i(tag, message ) } } //iosMai n actual val httpEngine = Darwin.create( ) actual val logger = Logger.DEFAULT
  10. iOS Source: https://dev.to/touchlab/kotlin-coroutines-and-swift-revisited-j5h func createPublisher<T>(flowAdapter: FlowAdapter<T>) -> AnyPublisher<T, KotlinError> {

    return Deferred<Publishers.HandleEvents<PassthroughSubject<T, KotlinError>>> { let subject = PassthroughSubject<T, KotlinError>( ) 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( ) }
  11. Shared iOS class FlowAdapter<T : Any> ( private val scope:

    CoroutineScope , private val flow: Flow<T > ) { fun subscribe ( onEvent: (T) -> Unit , onError: (Throwable) -> Unit , onComplete: () -> Uni t ): Job = flo w .onEach { onEvent(it) } .catch { onError(it) } .onCompletion { onComplete() } .launchIn(scope ) }
  12. 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()
  13. Next steps • Use async/await in iOS for suspend functions

    • Try crashlytics, analytics, etc • Use KMM for web app • Share more assets
  14. KMM - Pros and Cons • 100% Native • Android

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

    developers know 
 Kotlin and Gradle • Optional • Still in Alpha • UI written separately