SHIPPING MULTIPLATFORM ON IOS & ANDROID BEN ASHER ALEC STRONG @benasher44 @strongolopolis Copenhagen Denmark
SHIPPING MULTIPLATFORM ON IOS & ANDROID BEN ASHER ALEC STRONG @benasher44 @strongolopolis Copenhagen Denmark
Markups Link to another sheet
History
Live Info
Championed on iOS Championed on Android ~15 across Android and iOS ~30 across Android and iOS Construction Financial Services
Offline Search Sync System Business Logic
Business Logic Models
MPP Business Logic Models
MPP Business Logic Models
MPP
MPP Kotlin/Native
New tools 🌯🌯🌯 New language 🌯🌯🌯 Uneven ecosystem
Adopting cross-platform in your app has a cost
What is the value that makes it worth the effort?
Diverging Sync Systems
Existing Shared Code
What tools will your cross-platform solution need What is the common goal all stakeholders have
What else? No JNI on Android Obj-C Community Interop Not New Jetbrains For Android
Common Questions Kotlin/Native relies on Obj-C? What about Swift? Obj-C header is well-annotated for Swift This is the status quo for Apple’s own frameworks
Common Questions What about performance? Same performance expectations on Android Can drop into C if needed on iOS
Common Questions What about value types?
Common Questions There’s a garbage collector? But there’s no JVM, right? Bacon’s Algorithm for GC
Common Questions What libraries can we use for mpp? JVM Distributions are incomplete Special multiplatform distributions K/N ABI instability (klib)
Common Questions What is “common” code?
Library Outputs Library Source Sets iosMain iOS - .framework commonMain Android - .aar androidMain
Library Outputs Library Source Sets Code actual class Lock iosMain iOS - .framework expect class Lock commonMain Android - .aar actual class Lock androidMain
Library Outputs Library Source Sets actual class Lock iosMain iOS - .framework commonMain expect class Lock Android - .aar androidMain actual class Lock
Library Outputs Library Source Sets iosMain iOS - .framework commonMain Android - .aar androidMain
Library Outputs Library Source Sets iosMain iOS - .framework commonMain Android - .aar androidMain
Library Outputs Library Source Sets iosMain iOS - .framework commonMain Android - .aar androidMain Windows - .dll windowsMain
commonMain Yes Platform Yes Is it platform agnostic? No Will other common No code use it?
commonMain Yes Platform Yes Is it platform agnostic? No Will other common No code use it?
commonMain Yes Is it platform agnostic? Will other common No code use it?
expect class DatabaseFactory { internal fun createSqliteDriver(): SqlDriver }
Platform Is it platform agnostic? No Will other common No code use it?
sourceSets {A commonMain {B}C androidLibMain {D}E iosLibMain {F}G }H
sourceSets {A commonMain {B}C androidLibMain {D dependencies {I api project(‘:protos') }J }E iosLibMain {F}G }H
fun SearchQueries.insert(entity: SyncEntity) entity: SyncEntity
sourceSets {A commonMain {B}C androidLibMain {D}E iosLibMain {F}G }H
sourceSets {A commonMain {B}C androidLibMain {D}E }H
commonMain Yes Is it platform agnostic?
fun search(terms: String): List<Search>?
Library Source Sets iosMain Host commonMain Application androidMain
Host Application Talks to backend API Talks to existing application state
Library Initialization Host commonMain Application Library defines interface App provides implementation
Case Study: Networking Highly platform-specific Backend API Hosts Current user’s credential
MPP Life Hack: Interface Network Make an API request Receive an async response Interface can be re-implemented later
interface Network { /** * Makes an API request */ fun makeRequest( host: APIHost, request: Request, completion: (Request.Response) -> Unit ): NetworkDisposable }
interface Network { /** * Makes an API request */ fun makeRequest( host: APIHost, host: APIHost request: Request, completion: (Request.Response) -> Unit ): NetworkDisposable } Plain enum of names of the hosts– no URL handling required
data class: interface Network { - String path and body, /** * Makes an API request - query params dict */ fun makeRequest( - enum method (GET, POST, etc.) host: APIHost, request: Request, request: Request completion: (Request.Response) -> Unit ): NetworkDisposable }
interface Network { /** * Makes an API request */ fun makeRequest( host: APIHost, request: Request, completion: (Request.Response) -> Unit completion: (Request.Response) -> Unit ): NetworkDisposable } sealed class w/ different response types:
interface Network { /** * Makes an API request */ fun makeRequest( host: APIHost, request: Request, completion: (Request.Response) -> Unit ): NetworkDisposable NetworkDisposable } Interface with a cancel() method
suspend fun Network.makeRequest( host: APIHost, request: Request ): Request.Response
Concurrency in commonMain actual fun <T> T.freeze(): T = freeze() iosMain expect fun <T> T.freeze(): T commonMain actual fun <T> T.freeze(): T = this androidMain
Shipping Internally iosMain Host commonMain Application androidMain
iosMain Host Host commonMain Application Application androidMain
iosMain Host Host commonMain Application Application androidMain
Pros Cons Minimal change for android Two build environments for iOS Seamless local development CI Nightmare Single source of truth Host dependencies
iosMain Host Host commonMain Application Application androidMain
iosMain Host Host commonMain Application Application androidMain
iosMain Host Host commonMain commonMain Application Application androidMain
Host Host Application Application commonMain commonMain androidMain iosMain
Pros Cons Minimal change for android Two build environments for iOS Seamless local development CI Nightmare for iOS Host dependencies Out of sync code
iosMain Host Host commonMain Application Application androidMain
Host Host Application Application iosMain commonMain androidMain
Pros Cons Breaking changes are painful CI is fast Need good CD setup Encourages unit test coverage Debugging decoupled repos Single source of truth
iosMain Host Host commonMain Application Application androidMain
iosMain Host Host Application Application commonMain androidMain
commonMain Host Host Application Application androidMain
Shipping Externally Test Crashing!
Shipping Externally fun crash() { (null as String?)!! }
Shipping Externally setUnhandledExceptionHook()
Shipping Externally What happens if a Worker crashes?
Review Define your project’s goals Be thoughtful growing your codebase Avoid solving general muliplatform problems Get your CI and dev setup working early Join us in Kotlin slack
THANK YOU AND REMEMBER TO VOTE Ben Asher @benasher44 Alec Strong @Strongolopolis #KotlinConf
Recommend
More recommend