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 🛠 Architect | Entrepreneur 🔊 Speaker 🌎 Globe

    Tro tt er 🍻 Beer enthusiast @aditlal 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. l atf A ndroid Ko tl in / J V

    M iOS S w i ft / LLV M W e b J S D e sk t op Ko tl in / J V M
  4. Di f f e r e n t d e

    vic e s Di f f e r e n t p l atf orm Di f f e r e n t ch a l l e ng e s
  5. U I Compon e n t s ( Compos e

    ) U I Compon e n t s ( S w i f t UI ) A ppro a ch #1 P l atf orm I ndividu a l U I Compon e n t s
  6. e I A ppro a ch #2 P l atf

    orm - Som e sh a r e d UI Compon e n t s Sh a r e d UI ( Compos e ) l atf I a l U I e t
  7. A ppro a ch #3 P l atf orm -

    all sh a r e d UI Compon e n t s Sh a r e d UI ( Compos e ) e I l atf e a e UI e t
  8. l atf e a e UI e t - [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)
  9. 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() } } l atf e a e UI e t
  10. l atf e a e UI e t class MainActivity

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

    { va r body: some Scene { WindowG r oup { ContentView() } } } l atf e a e UI e t
  12. 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) } }
  13. 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) } }
  14. NavigationView { ZStack { ComposeView() } }.toolba r { /

    / .... } Sh a r e d UI compon e n t
  15. 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) } }
  16. 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) } }
  17. src a ndroid A pp iOS A pp sh a

    r e d S t ruc t ur e
  18. sh a r e d S t ruc t ur

    e src common M a in a ndroid Ma in iOS Ma in bui l d.gr a d l e .k t s
  19. plugins { kotlin("multiplatform") } val commonMain by getting { dependencies

    { implementation(compose.ui) implementation(compose.foundation) implementation(compose.material) implementation(compose.runtime) } } sh a r e d
  20. @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() } }
  21. @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() } }
  22. @Composable fun AppContent() { if (LocalWindowSize.cu r r ent =

    = WindowSize.COMPACT) { Column { / * content * / } } else { Row { / * content * / } } }
  23. 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() } M oko - M VV M
  24. 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() } A rchi t e c t ur e
  25. @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 } A rchi t e c t ur e
  26. UI - I m a g e l o a

    ding a nd c a ch e
  27. A nim a t ion & I n t e

    r a c t ivi ty - E xp l oring t h e Po t e n t i al ( w i t h C a v eat s) 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
  28. A nim a t ion & I n t e

    r a c t ivi ty - E xp l oring t h e Po t e n t i al ( w i t h C a v eat s) 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) } }
  29. A nim a t ion & I n t e

    r a c t ivi ty - E xp l oring t h e Po t e n t i al ( w i t h C a v eat s) 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) } }
  30. D at a A cc e ss & N etw

    orking - K t or & Room M u lt ip l at f orm Po we rhous e 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.
  31. D at a A cc e ss & N etw

    orking - K t or & Room M u lt ip l at f orm Po we rhous e 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) } }
  32. D at a A cc e ss & N etw

    orking - K t or & Room M u lt ip l at f orm Po we rhous e 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) } }
  33. A dv a nc e d U I Bui l

    ding B l ocks - Pix el -P e r f e c t C a nv a s in A c t ion 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.
  34. 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()) } A dv a nc e d U I Bui l ding B l ocks - Pix el -P e r f e c t C a nv a s in A c t ion
  35. 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()) } A dv a nc e d U I Bui l ding B l ocks - Pix el -P e r f e c t C a nv a s in A c t ion
  36. A dv a nc e d U I Bui l

    ding B l ocks - Pix el -P e r f e c t C a nv a s in A c t ion 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.
  37. Sm a l l a c t ions - ho

    w t o cross t h e p l atf orm l in e s 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> }
  38. Sm a l l a c t ions - ho

    w t o cross t h e p l atf orm l in e s 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() } }
  39. Sm a l l a c t ions - sh

    a r e d a cross p l a tf orms 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() } } } }
  40. Sm a l l a c t ions - sh

    a r e d a cross p l a tf orms 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) }
  41. G o t ch a s - iOS Explore the

    expressive power of Compose a
  42. G o t ch a s - good on A

    ndroid Explore the expressive power of Compose a .focusP r ope r ties { canFocus = false }
  43. • Sta rt Small, Scale Sma rt : ‣ 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 commi tt ing fully. • Leverage Common Code: ‣ Compose Multipla tf orm's core strength lies in code sharing across pla tf orms. ‣ Focus on building reusable components and logic in commonMain to maximize e ffi ciency and maintainability. Tips
  44. • Embrace the Latest: ‣ JetBrains actively improves Compose Multipla

    tf orm. Stay updated with the latest libraries and tools (e.g., Compose 1.3.0) to bene fi t from bug fi xes, pe rf ormance enhancements, and new features. • Test Rigorously: ‣ Testing is crucial for any app, even more so for multipla tf orm ones. Utilize comprehensive testing strategies like unit, integration, and UI testing to ensure your Compose code functions fl awlessly across pla tf orms. Tips
  45. • 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
  46. Kotlin multipla tf orm - 🛠 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
  47. Kotlin Slack Kotlin O ffi cial Kotlin Training h tt

    ps://kotlinlang.org/docs/multipla tf orm-publish-lib.html Kotlin by: - h tt ps://jakewha rt on.com/presentations/ - h tt p://antonioleiva.com/kotlin Pay attention to this Resources
  48. Kotlin multipla tf orm h tt ps://github.com/joreilly/PeopleInSpace h tt ps://github.com/joreilly/ClimateTraceKMP

    h tt ps://github.com/halcyonmobile/Multipla tf ormPlayground h tt ps://github.com/msasikanth/twine (Compose Shared UI) h tt ps://slackhq.github.io/circuit/ h tt ps://github.com/chrisbanes/haze h tt ps://github.com/dhis2/dhis2-mobile-ui Kotlin Multipla tf orm project with Swi ft UI, Jetpack Compose, Wear Compose, Compose for Desktop, Compose for Web and Kotlin/JS + React clients along with Ktor backend. Resources
  49. 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