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

Elevating Cross-Platform Excellence: Compose Unleashed with Kotlin Multiplatform

Adit Lal
December 14, 2023

Elevating Cross-Platform Excellence: Compose Unleashed with Kotlin Multiplatform

This talk delves into the dynamic synergy of Compose and KMP, exploring how this duo can revolutionize cross-platform development while addressing key production challenges and performance optimization.

In this session, we will embark on a journey that fuses the expressive power of Compose with the code-sharing prowess of KMP. Drawing from over a decade of Android and Kotlin experience, I will guide you through practical solutions for harnessing this powerful combination.
- Compose and KMP Integration
- Production ready compose and gotchas
- Performance enchamements
- Compatibility and Migration
- Tests and release

Adit Lal

December 14, 2023
Tweet

More Decks by Adit Lal

Other Decks in Technology

Transcript

  1. Android GDE - @aditlal
 🛠 Architect | Entrepreneur 🔊 Speaker


    🌎 Globe Trotter
 🍻 Beer enthusiast Adit GDE
  2. Coroutines Null-Safety Extension-Functions Smart-Casts Delegated-Properties Versatility on the Server Side

    streamlined approach Open Source Modern Language Features Uni fi ed Codebase
  3. UI Components ( Compose) UI Components ( Swift UI) Approach

    #2 Platform - Some shared UI Components Shared UI( Compose)
  4. Approach #3 Platform - all shared UI Components Shared UI(

    Compose) UI Components ( Compose) UI Components ( Swift UI)
  5. - [Ktor](https://ktor.io/) - [Koin](https://insert-koin.io/) - [Kotlinx Serialization](https://kotlinlang.org/docs/serialization.html) - [Kotlinx Coroutines](https://kotlinlang.org/docs/coroutines-overview.html)

    - [Compose ImageLoader](https://github.com/qdsfdhvh/compose-imageloader) - [KMM-ViewModel](https://github.com/rickclephas/KMM-ViewModel) - [Multiplatform Settings](https://github.com/russhwolf/multiplatform-settings)
  6. ZStack { LazyVG r id( . . . ) {

    Fo r Each(id: .id) { item in Image(item.u r l) . r ende r ingMode(.o r iginal) . r esizable() .scaledToFill() } }.task { await r eposito r y.getImages() } }
  7. class MainActivity : AppCompatActivity() { ove r r ide fun

    onC r eate(savedInstanceState: Bundle?) { supe r .onC r eate(savedInstanceState) setContent { MainView() } } }
  8. impo r t SwiftUI @main st r uct iOSApp: App

    { va r body: some Scene { WindowG r oup { ContentView() } } } st r uct ContentView: View { va r body: some View { Text("Hello, wo r ld!") .padding() } }
  9. st r uct ComposeView: UIViewCont r olle r Rep r

    esentable { func makeUIViewCont r olle r (context: Context) - > UIViewCont r olle r { r etu r n Main_iosKt.MainViewCont r olle r () } func updateUIViewCont r olle r (_ uiViewCont r olle r : UIViewCont r olle r , context: Context) {} } st r uct ContentView: View { va r body: some View { ComposeView() .igno r esSafeA r ea(.all) } }
  10. st r uct ComposeView: UIViewCont r olle r Rep r

    esentable { func makeUIViewCont r olle r (context: Context) - > UIViewCont r olle r { r etu r n Main_iosKt.MainViewCont r olle r () } func updateUIViewCont r olle r (_ uiViewCont r olle r : UIViewCont r olle r , context: Context) {} } st r uct ContentView: View { va r body: some View { ComposeView() .igno r esSafeA r ea(.all) } }
  11. p r ivate val cache: MutableMap<St r ing, Font> =

    mutableMapOf() @OptIn(Expe r imentalResou r ceApi : : class) @Composable actual fun font(name: St r ing, r es: St r ing, weight: FontWeight, style: FontStyle) : Font { r etu r n cache.getO r Put( r es) { val byteA r r ay = r unBlocking { r esou r ce("font/$ r es.ttf"). r eadBytes() } and r oidx.compose.ui.text.platfo r m.Font( r es, byteA r r ay, weight, style) } }
  12. p r ivate val cache: MutableMap<St r ing, Font> =

    mutableMapOf() @OptIn(Expe r imentalResou r ceApi : : class) @Composable actual fun font(name: St r ing, r es: St r ing, weight: FontWeight, style: FontStyle) : Font { r etu r n cache.getO r Put( r es) { val byteA r r ay = r unBlocking { r esou r ce("font/$ r es.ttf"). r eadBytes() } and r oidx.compose.ui.text.platfo r m.Font( r es, byteA r r ay, weight, style) } }
  13. plugins { kotlin("multiplatform") } val commonMain by getting { dependencies

    { implementation(compose.ui) implementation(compose.foundation) implementation(compose.material) implementation(compose.runtime) } } shared
  14. @Composable fun CustomTheme( windowSize: WindowSize, da r kTheme: Boolean =

    isSystemInDa r kTheme(), content: @Composable () - > Unit, ) { CompositionLocalP r ovide r ( LocalCustomColo r s p r ovides if (da r kTheme) da r kColo r s else lightColo r s, LocalWindowSize p r ovides windowSize ) { content() } }
  15. @Composable fun CustomTheme( windowSize: WindowSize, da r kTheme: Boolean =

    isSystemInDa r kTheme(), content: @Composable () - > Unit, ) { CompositionLocalP r ovide r ( LocalCustomColo r s p r ovides if (da r kTheme) da r kColo r s else lightColo r s, LocalWindowSize p r ovides windowSize ) { content() } }
  16. @Composable fun AppContent() { if (LocalWindowSize.cu r r ent =

    = WindowSize.COMPACT) { Column { / * content * / } } else { Row { / * content * / } } }
  17. sealed class UiState { object Loading: UiState() data class Success(val

    images: List<ImageData>) : UiState() data class E r r o r (val e r r o r Message: St r ing) : UiState() } Moko -MVVM
  18. sealed class UiState { object Loading: UiState() data class Success(val

    images: List<ImageData>) : UiState() data class E r r o r (val e r r o r Message: St r ing) : UiState() } Architecture
  19. @Composable ove r r ide fun models(events: F l ow<Event>)

    : UiState { val uiState = r emembe r { mutableStateOf(UIState.Loading) } LaunchedEffect(Unit) { val imagesList = imagesReposito r y.getImages() uiState.value = UIState.Success(imagesList) } r etu r n uiState } Architecture
  20. Animation & Interactivity - Exploring the Potential (with Caveats) Explore

    the expressive power of Compose a nim a tion APIs, but be mindful of pl a tform-speci fi c consider a tions for complex a nim a tions a nd custom libr a ries. • a nim a te*AsSt a te •upd a teTr a nsition •Anim a t a ble with a nim a teTo or sn a pTo
  21. Animation & Interactivity - Exploring the Potential (with Caveats) Explore

    the expressive power of Compose a @Composable fun AnimatedButton(text: String, onClick: () -> Unit) { val isClicked = remember { false } Button( modi fi er = Modi fi er . fi llMaxWidth() .animateAsState( targetValue = if (isClicked) Color.Green else Color.Blue, animationSpec = tween(durationMillis = 200) ), onClick = { isClicked = !isClicked onClick() } ) { Text(text) } }
  22. Animation & Interactivity - Exploring the Potential (with Caveats) Explore

    the expressive power of Compose a @Composable fun AnimatedButton(text: String, onClick: () -> Unit) { val isClicked = remember { false } Button( modi fi er = Modi fi er . fi llMaxWidth() .animateAsState( targetValue = if (isClicked) Color.Green else Color.Blue, animationSpec = tween(durationMillis = 200) ), onClick = { isClicked = !isClicked onClick() } ) { Text(text) } }
  23. Explore the expressive power of Compose a Unle a sh

    the power of d a t a with Ktor's pl a tform- a gnostic networking a nd Room Multipl a tform's sh a red persistence a cross pl a tforms This dyn a mic duo simpli fi es d a t a a ccess a nd empowers you to build d a t a -driven experiences th a t work se a mlessly everywhere. Data Access & Networking - Ktor & Room Multiplatform Powerhouse
  24. Data Access & Networking - Ktor & Room Multiplatform Powerhouse

    Explore the expressive power of Compose a Unle a This dyn a / / Kto r netwo r k call suspend fun getImages() : List<ImageModels> { val r esponse = kto r Client.get("https: / / api.example.com/models") val use r = r esponse.body<Use r >() r etu r n use r } / / Room Multiplatfo r m data access fun saveImages(model: ImageModels) { r unBlocking { database.imageDao().inse r t(model) } }
  25. Explore the expressive power of Compose a / / Kto

    r netwo r k call suspend fun getImages() : List<ImageModels> { val r esponse = kto r Client.get("https: / / api.example.com/models") val use r = r esponse.body<Use r >() r etu r n use r } / / Room Multiplatfo r m data access fun saveImages(model: ImageModels) { r unBlocking { database.imageDao().inse r t(model) } } Data Access & Networking - Ktor & Room Multiplatform Powerhouse
  26. Advanced UI Building Blocks - Pixel-Perfect Canvas in Action Explore

    the expressive power of Compose a Be subjective to a d a ptive l a youts - remember its di ff erent pl a tform a t pl a y, things c a n bre a k. Witness a nd experiment with the power of modi fi ers, pl a cement, a nd dr a wing a ttention to cr a ft unique a nd a d a pt a ble UI experiences.
  27. Explore the expressive power of Compose a Unle a This

    dyn a Box(modif i e r = Modif i e r .f i llMaxSize() .offset { ca r dPositions.value[ca r ds.f i r st().id] ?: Offset.Ze r o } / / Implement d r agging gestu r e fo r ca r d r e - o r de r ing .d r aggable(enabled = t r ue) { d r agDelta, _ - > ca r dPositions.value = ca r dPositions.value.toMutableMap().apply { / / Update ca r d positions based on d r ag delta val newPositions = ca r ds.mapIndexed { index, ca r d - > ca r d.id to Offset(d r agDelta.x * index, d r agDelta.y * index) }.toMap() putAll(newPositions) } } ) { Image(ca r d.image, modif i e r = Modif i e r .f i llMaxSize()) } Advanced UI Building Blocks - Pixel-Perfect Canvas in Action
  28. Explore the expressive power of Compose a Unle a This

    dyn a Box(modif i e r = Modif i e r .f i llMaxSize() .offset { ca r dPositions.value[ca r ds.f i r st().id] ?: Offset.Ze r o } / / Implement d r agging gestu r e fo r ca r d r e - o r de r ing .d r aggable(enabled = t r ue) { d r agDelta, _ - > ca r dPositions.value = ca r dPositions.value.toMutableMap().apply { / / Update ca r d positions based on d r ag delta val newPositions = ca r ds.mapIndexed { index, ca r d - > ca r d.id to Offset(d r agDelta.x * index, d r agDelta.y * index) }.toMap() putAll(newPositions) } } ) { Image(ca r d.image, modif i e r = Modif i e r .f i llMaxSize()) } Advanced UI Building Blocks - Pixel-Perfect Canvas in Action
  29. Advanced UI Building Blocks - Pixel-Perfect Canvas in Action Explore

    the expressive power of Compose a •Complexity management: Break down complex layouts into smaller composables for maintainability and testing purposes. •Performance optimization: Pay attention to drawing calls and animations to avoid performance bottlenecks. •Accessibility: Ensure your custom layout is accessible by considering keyboard navigation and screen reader compatibility.
  30. Small actions - how to cross the platform lines Explore

    the expressive power of Compose a inte r face Sto r e { fun send(action: Action) val events: Sha r edF l ow<Action> }
  31. Small actions - how to cross the platform lines Explore

    the expressive power of Compose a fun Co r outineScope.c r eateSto r e() : Sto r e { val events = MutableSha r edF l ow<Action>() r etu r n object : Sto r e { ove r r ide fun send(action: Action) { launch { events.emit(action) } } ove r r ide val events: Sha r edF l ow<Action> = events.asSha r edF l ow() } }
  32. Small actions - shared across platforms Explore the expressive power

    of Compose a @Composable actual fun BackHandle r (isEnabled: Boolean, onBack: () - > Unit) { LaunchedEffect(isEnabled) { sto r e.events.collect { if(isEnabled) { onBack() } } } }
  33. Small actions - shared across platforms Explore the expressive power

    of Compose a @Composable expect fun BackHandle r (isEnabled: Boolean, onBack: () - > Unit) / / And r oid implementation @Composable actual fun BackHandle r (isEnabled: Boolean, onBack: () - > Unit) { BackHandle r (isEnabled, onBack) }
  34. Gotchas - good on Android Explore the expressive power of

    Compose a .focusP r ope r ties { canFocus = false }
  35. • Start Small, Scale Smart: ‣ Don't dive into rewriting

    your entire app at once. ‣ Begin with a small feature, like a login screen, and gradually expand your Compose footprint. This minimizes risk and allows you to iron out any kinks before committing fully. • Leverage Common Code: ‣ Compose Multiplatform's core strength lies in code sharing across platforms. ‣ Focus on building reusable components and logic in commonMain to maximize ef fi ciency and maintainability. Tips
  36. • Embrace the Latest: ‣ JetBrains actively improves Compose Multiplatform.

    Stay updated with the latest libraries and tools (e.g., Compose 1.6.x) to bene fi t from bug fi xes, performance enhancements, and new features. • Test Rigorously: ‣ Testing is crucial for any app, even more so for multiplatform ones. Utilize comprehensive testing strategies like unit, integration, and UI testing to ensure your Compose code functions fl awlessly across platforms. Tips
  37. • Community is Key: ‣ The Compose community thrives on

    collaboration. Join forums, Slack channels, and social media groups to learn best practices, troubleshoot issues, and stay informed about the latest developments. Tips
  38. Kotlin multiplatform - 🛠 Tooling
 📦 Storage
 🏗 Architecture 🔑

    Crypto
 🗃 Serializer - code and tools - 🍎 Compose UI 🧮 Arithmetic - 📋 Log
 📱 Device
 🔍 Analytics 📁 File
 ⏰ Date-Time - 🎨 Graphics 🛢 Resources - 🌎 Network
 💉 Dependency Injection 🩺 Test
 🚀 Language extensions ➿ Asynchronous
 🧩 Service SDK
 🔧 Utils
  39. Kotlin Slack Kotlin Of fi cial Kotlin Training https://kotlinlang.org/docs/multiplatform-publish-lib.html Kotlin

    by: - https://jakewharton.com/presentations/ - http://antonioleiva.com/kotlin Resources
  40. Kotlin multiplatform https://github.com/joreilly/PeopleInSpace https://github.com/joreilly/ClimateTraceKMP https://github.com/halcyonmobile/MultiplatformPlayground https://github.com/msasikanth/twine (Compose Shared UI) https://slackhq.github.io/circuit/

    https://github.com/chrisbanes/haze https://github.com/dhis2/dhis2-mobile-ui Kotlin Multiplatform project with SwiftUI, Jetpack Compose, Wear Compose, Compose for Desktop, Compose for Web and Kotlin/JS + React clients along with Ktor backend. Resources
  41. https://chrisb a nes.me/posts/swiftui-for-jetp a ck-compose-devs-st a te/ https://bumble-tech.github.io/ a ppyx/

    https://tl a ster.github.io/PreCompose/ https://github.com/terr a kok/kmp- a wesome https://github.com/AAkir a /Kotlin-Multipl a tform-Libr a ries https://github.com/exyte/ComposeMultipl a tformDribbbleAudio Resources
  42. Kotlin Multiplatform: Share Logic Across Mobile, Web, and Beyond -

    This video explores sharing logic across platforms using Kotlin Multiplatform. Compose Multiplatform: Building UIs for Android, iOS, and Beyond - Covers the basics of Compose Multiplatform and building UIs for various platforms. Advanced Kotlin Multiplatform: Tips and Tricks - Focuses on advanced usage of Kotlin Multiplatform with tips and strategies. Real-world Compose Multiplatform Applications - Discusses practical examples and bene fi ts of applications built with Compose Multiplatform. KotlinConf 2024