Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Building Shared UIs across Platforms with Compose
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Mohit S
September 16, 2023
Programming
1
680
Building Shared UIs across Platforms with Compose
Mohit S
September 16, 2023
Tweet
Share
More Decks by Mohit S
See All by Mohit S
Guide to Improving Compose Performance
heyitsmohit
0
290
Building Multiplatform Apps with Compose
heyitsmohit
2
570
Building StateFlows with Jetpack Compose
heyitsmohit
6
1.9k
Building Android Testing Infrastructure
heyitsmohit
1
560
Migrating to Kotlin State & Shared Flows
heyitsmohit
1
820
Using Square Workflow for Android & iOS
heyitsmohit
1
470
Building Android Infrastructure Teams at Scale
heyitsmohit
3
370
Strategies for Migrating to Jetpack Compose
heyitsmohit
2
620
Challenges of Building Kotlin Multiplatform Libraries
heyitsmohit
1
480
Other Decks in Programming
See All in Programming
Claude Codeと2つの巻き戻し戦略 / Two Rewind Strategies with Claude Code
fruitriin
0
140
AIエージェントのキホンから学ぶ「エージェンティックコーディング」実践入門
masahiro_nishimi
6
600
AI & Enginnering
codelynx
0
120
CSC307 Lecture 01
javiergs
PRO
0
690
AI時代の認知負荷との向き合い方
optfit
0
160
AI Agent の開発と運用を支える Durable Execution #AgentsInProd
izumin5210
7
2.3k
CSC307 Lecture 08
javiergs
PRO
0
670
AtCoder Conference 2025
shindannin
0
1.1k
AIと一緒にレガシーに向き合ってみた
nyafunta9858
0
250
CSC307 Lecture 04
javiergs
PRO
0
660
FOSDEM 2026: STUNMESH-go: Building P2P WireGuard Mesh Without Self-Hosted Infrastructure
tjjh89017
0
170
Package Management Learnings from Homebrew
mikemcquaid
0
230
Featured
See All Featured
Chasing Engaging Ingredients in Design
codingconduct
0
110
The Illustrated Children's Guide to Kubernetes
chrisshort
51
51k
ラッコキーワード サービス紹介資料
rakko
1
2.3M
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
71
Optimising Largest Contentful Paint
csswizardry
37
3.6k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
240
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.3k
Between Models and Reality
mayunak
1
190
Thoughts on Productivity
jonyablonski
74
5k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
9.9k
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
Navigating the Design Leadership Dip - Product Design Week Design Leaders+ Conference 2024
apolaine
0
190
Transcript
Mohit Sarveiya Building Shared UIs Across Platforms with Compose @heyitsmohit
Building Shared UIs Across Platforms with Compose • Setup &
Architecture
Building Shared UIs Across Platforms with Compose • Setup &
Architecture • Internals
Building Shared UIs Across Platforms with Compose • Setup &
Architecture • Internals • Interop with iOS
Android Kotlin/JVM iOS Swift/LLVM Web JS Desktop Kotlin/JVM
API Share Cache Business Logic Platforms
API Share Cache Business Logic UI Components Platforms
https: / / github.com/JetBrains/compose-multiplatform compose-multiplatform
Approaches • Share all UI components Components (Compose)
Approaches UI Components (Compose) • Share individual UI components UI
Components (SwiftUI)
Approaches UI Components (Compose) • Share individual UI components UI
Components (SwiftUI) Shared Components (Compose)
Approaches • Share individual UI components • Share all UI
components
Example • SwiftUI App • Display list of images
Example • SwiftUI App • Display list of images •
Details page
Goal • Display list of images (Compose) • Details page
(SwiftUI)
Goal ZStack { LazyVGrid( ... ) { ForEach(id: .id) {
item in Image(item.url) .renderingMode(.original) .resizable() .scaledToFill() } }.task { await repository.getImages() } }
UI Structure Shared Component NavigationView { ZStack { ComposeView() }
}.toolbar { ... }
https: / / github.com/JetBrains/compose-multiplatform-template compose-multiplatform
androidApp iOSApp shared Structure
shared src commonMain androidMain iOSMain Shared Module
shared src commonMain androidMain iOSMain build.gradle.kts Shared Module
plugins { kotlin("multiplatform") } val commonMain by getting { dependencies
{ implementation(compose.ui) implementation(compose.foundation) implementation(compose.material) implementation(compose.runtime) } } Shared Module
plugins { kotlin("multiplatform") } val commonMain by getting { dependencies
{ implementation(compose.ui) implementation(compose.foundation) implementation(compose.material) implementation(compose.runtime) } } Shared Module
UI Structure
UI Structure Shared Component NavigationView { ZStack { ComposeView() }
}.toolbar { ... }
UI Structure Compose View Images List ViewController AppTheme NavigationView {
ZStack { ComposeView() } }.toolbar { ... }
shared src commonMain androidMain iOSMain ImagesAppTheme.kt Shared Module
App Theme
App Theme val LightColorPalette = lightColors( ... ) val DarkColorPalette
= darkColors( ... ) @Composable fun ImagesAppTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { MaterialTheme( colors = if (darkTheme) DarkColorPalette else LightColorPalette, content = content )
App Theme @Composable fun ImagesAppTheme( darkTheme: Boolean = isSystemInDarkTheme(), content:
@Composable () -> Unit ) { MaterialTheme( colors = if (darkTheme) DarkColorPalette else LightColorPalette, content = content ) }
App Theme @Composable fun ImagesAppTheme( darkTheme: Boolean = isSystemInDarkTheme(), content:
@Composable () -> Unit ) { MaterialTheme( colors = if (darkTheme) DarkColorPalette else LightColorPalette, content = content ) }
UI Structure Compose View Images List ViewController AppTheme NavigationView {
ZStack { ComposeView() } }.toolbar { ... }
UI Structure struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context)
-> UIViewController { let controller = Main_iosKt.MainiOS() return controller } }
UI Structure struct ComposeView: UIViewControllerRepresentable { func makeUIViewController( ...
) -> UIViewController { let controller = Main_iosKt.MainiOS() return controller } }
UI Structure struct ComposeView: UIViewControllerRepresentable { func makeUIViewController( ...
) -> UIViewController { let controller = ImagesList() return controller } }
UI Structure fun ImagesList(): UIViewController = ComposeUIViewController { ImagesAppCommon() }
UI Structure fun ImagesList(): UIViewController = ComposeUIViewController { ... }
Compose Architecture Compose Multiplatform Compose Multiplatform Core
https: / / github.com/JetBrains/compose-multiplatform-core compose-multiplatform
Multiplatform Core fun ComposeUIViewController( content: @Composable () -> Unit ):
UIViewController = ComposeWindow().apply { configuration = ComposeUIViewControllerConfiguration() .apply(configure) setContent(content) }
Multiplatform Core fun ComposeUIViewController( content: @Composable () -> Unit ):
UIViewController = ComposeWindow().apply { setContent(content) }
UI Structure Compose View Images List ViewController AppTheme NavigationView {
ZStack { ComposeView() } }.toolbar { ... }
UI Structure fun ImagesList(): UIViewController = ComposeUIViewController { ... }
UI Structure fun ImagesList(): UIViewController = ComposeUIViewController { AppTheme {
} } Text("Hello World")
UI Structure struct ContentView: View { var body: some
View { ZStack { ComposeView() ... } } } Hello World
UI Structure UIWindowScene UIWindow ComposeWindow SkikkoUIView View Hierarchy struct ContentView:
View { var body: some View { ZStack { ComposeView() ... } } } UIWindowScene UIWindow ComposeWindow SkikkoUIView
UI Structure UIWindowScene UIWindow ComposeWindow SkikkoUIView View Hierarchy struct ContentView:
View { var body: some View { ZStack { ComposeView() ... } } } UIWindowScene UIWindow ComposeWindow SkikkoUIView
UI Structure UIWindowScene UIWindow ComposeWindow SkikkoUIView View Hierarchy struct ContentView:
View { var body: some View { ZStack { ComposeView() ... } } } UIWindowScene UIWindow ComposeWindow SkikkoUIView
https: / / github.com/JetBrains/skiko Skiko
Compose Multiplatform Architecture Skia Skiko Compose UIViewController UIKit SwiftUI
UI Structure UIWindowScene UIWindow ComposeWindow SkikkoUIView View Hierarchy struct ContentView:
View { var body: some View { ZStack { ComposeView() ... } } } UIWindowScene UIWindow ComposeWindow SkikkoUIView
UI Structure class ComposeWindow : UIViewController { var layer: ComposeLayer
var content: @Composable () -> Unit override fun loadView() { ... } }
UI Structure fun ImagesList(): UIViewController = ComposeUIViewController { AppTheme {
} } Text("Hello World")
Compose Multiplatform Architecture Skia Skiko Compose UIViewController UIKit SwiftUI
UI Structure class ComposeWindow : UIViewController { override fun loadView()
{ val skiaLayer = createSkiaLayer() val skikoUIView = SkikoUIView(skiaLayer = skiaLayer).load() val rootView = UIView() rootView.addSubview(skikoUIView) } }
UI Structure class ComposeWindow : UIViewController { override fun loadView()
{ val skiaLayer = createSkiaLayer() val skikoUIView = SkikoUIView(skiaLayer = skiaLayer).load() val rootView = UIView() rootView.addSubview(skikoUIView) layer = ComposeLayer(layer = skiaLayer) layer.setContent( CompositionLocalProvider( ... ) { content() } } ) } }
UI Structure struct ContentView: View { var body: some
View { ZStack { ComposeView() ... } } } Hello World
Architecture
UI Structure struct ContentView: View { var body: some
View { ZStack { ComposeView() ... } } }
Architecture View Repo View Model SwiftUI ComposeView Shared
View Model Repository View Request Response UI State Event
https: / / github.com/cashapp/molecule Molecule
@Composable fun Presenter(): Model State Flow Compose Runtime Recomposition
@Composable fun Presenter(): Model State Flow Recomposition Monotomic Frame Clock
Molecule Muiltiplatform Support • Android (all versions) • JS (0.3.0
and newer) • JVM (0.3.0 and newer) • iOS (0.5.0-beta01 and newer) • MacOS (0.5.0-beta01 and newer)
https: / / github.com/icerockdev/moko-mvvm Moko
Architecture sealed class UiState { object Loading: UiState() data class
Success( val images: List<ImageData> ): UiState() data class Error( val errorMessage: String ): UiState() }
Architecture abstract class MoleculeViewModel <> : ViewModel() { }
Architecture abstract class MoleculeViewModel<Model, Event >: ViewModel() { }
Architecture abstract class MoleculeViewModel<Model, Event >: ViewModel() { }
Architecture abstract class MoleculeViewModel: ViewModel() { val scope = CoroutineScope(
viewModelScope.coroutineContext ) }
Architecture Frame Clock DisplayLinkClock iOS AndroidUiFrameClock Android
https: / / developer.apple.com/documentation/quartzcore/cadisplaylink CADisplayLink
Architecture abstract class MoleculeViewModel: ViewModel() { val scope = CoroutineScope(
viewModelScope.coroutineContext + DisplayLinkClock ) }
Architecture object DisplayLinkClock : MonotonicFrameClock { val displayLink: CADisplayLink =
val clock = BroadcastFrameClock { ... } override suspend fun withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R { return clock.withFrameNanos(onFrame) } }
Architecture abstract class MoleculeViewModel: ViewModel() { val scope = CoroutineScope(…)
val models: StateFlow<Model> by lazy(…) { scope.launchMolecule(mode = RecompositionMode.ContextClock) { models(…) } } }
Architecture abstract class MoleculeViewModel: ViewModel() { val scope = CoroutineScope(…)
val models: StateFlow<Model> by lazy(…) { scope.launchMolecule(mode = RecompositionMode.ContextClock) { models(…) } } }
Architecture abstract class MoleculeViewModel: ViewModel() { val scope = CoroutineScope(…)
val models: StateFlow<Model> by lazy(…) { scope.launchMolecule(mode = RecompositionMode.ContextClock) { models(…) } } }
View Model View UI State Event
Architecture abstract class MoleculeViewModel: ViewModel() { val events = MutableSharedFlow<Event>(extraBufferCapacity
= 20) fun take(event: Event) { if (!events.tryEmit(event)) { error("Event buffer overflow.") } } }
Architecture abstract class MoleculeViewModel: ViewModel() { val events = MutableSharedFlow<Event>(extraBufferCapacity
= 20) fun take(event: Event) { if (!events.tryEmit(event)) { error("Event buffer overflow.") } } }
Architecture View Repo View Model SwiftUI ComposeView Shared
Architecture class ImagesViewModel: MoleculeViewModel() { }
Architecture class ImagesViewModel: MoleculeViewModel() { @Composable override fun models(events: Flow<Event>):
UiState { } }
Architecture @Composable override fun models(events: Flow<Event>): UiState { var uiState
by remember { mutableStateOf(UIState.Loading) } }
Architecture @Composable override fun models(events: Flow<Event>): UiState { var uiState
by remember { mutableStateOf(UIState.Loading) } LaunchedEffect(Unit) { } }
Architecture @Composable override fun models(events: Flow<Event>): UiState { var uiState
by remember { mutableStateOf(UIState.Loading) } LaunchedEffect(Unit) { val imagesList = imagesRepository.getImages() uiState = UIState.Success(imagesList) } }
Architecture @Composable override fun models(events: Flow<Event>): UiState { var uiState
by remember { mutableStateOf(UIState.Loading) } LaunchedEffect(Unit) { val imagesList = imagesRepository.getImages() uiState = UIState.Success(imagesList) } return uiState }
Architecture fun ImagesList(): UIViewController = ComposeUIViewController { AppTheme {
} }
Architecture ComposeUIViewController { AppTheme { val viewModel = getViewModel(…,
viewModelFactory { ImagesViewModel() }) } }
Architecture ComposeUIViewController { AppTheme { val viewModel = getViewModel(…)
val model by viewModel.models.collectAsState() } }
Architecture ComposeUIViewController { AppTheme { val viewModel = getViewModel(…)
val model by viewModel.models.collectAsState() ImagesList(model) } }
Architecture View Repo View Model SwiftUI ComposeView Shared
Architecture fun ImagesList(model: UiState) { Column { LazyVerticalGrid { items(images)
{ ... } } } }
https: / / github.com/Kamel-Media/Kamel Kamel
Architecture fun ImagesList(model: UiState) { Column { LazyVerticalGrid { items(images)
{ KamelImage( asyncPainterResource(image.path), contentScale = ContentScale.Crop, ) } }
UI Structure struct ContentView: View { var body: some
View { ZStack { ComposeView() ... } } }
Architecture View Repo View Model SwiftUI ComposeView Shared
iOS Interop
• Compose in SwiftUI Interop
• Compose in SwiftUI • SwiftUI in Compose Interop
Interop View in Compose
Interop SwiftUI View View in Compose
Interop Compose View SwiftUI View Provide
Interop shared src commonMain androidMain iOSMain App Screen
Interop fun AppScreen(createUIView: () -> UIView): UIViewController = ComposeUIViewController {
Column( horizontalAlignment = Alignment.CenterHorizontally ) { Text("How to use SwiftUI inside Compose") UIKitView( factory = createUIView, modifier = Modifier.size(300.dp).border(2.dp, Color.Blue), ) } }
Interop fun AppScreen(createUIView: () -> UIView): UIViewController = ComposeUIViewController {
Column( horizontalAlignment = Alignment.CenterHorizontally ) { Text("How to use SwiftUI inside Compose") UIKitView( factory = createUIView, modifier = Modifier.size(300.dp).border(2.dp, Color.Blue), ) } }
Interop fun AppScreen(createUIView: () -> UIView): UIViewController = ComposeUIViewController {
Column( horizontalAlignment = Alignment.CenterHorizontally ) { Text(“View in Compose”) UIKitView( factory = createUIView, modifier = Modifier.size(300.dp).border(2.dp, Color.Blue), ) } }
Interop fun AppScreen(createUIView: () -> UIView): UIViewController = ComposeUIViewController {
Column( horizontalAlignment = Alignment.CenterHorizontally ) { Text(“View in Compose”) UIKitView( factory = createUIView, modifier = Modifier.size(300.dp).border(2.dp, Color.Blue), ) } }
Interop App Screen SwiftUI View Provide
androidApp iOSApp desktopApp shared Interop
Interop struct ComposeView: UIViewControllerRepresentable { func makeUIViewController( ... )
-> UIViewController { AppScreen( VStack { Text(“Compose View”) } ) } }
Interop struct ComposeView: UIViewControllerRepresentable { func makeUIViewController( ... )
-> UIViewController { AppScreen( VStack { Text(“Compose View”) } ) } }
Interop struct ComposeView: UIViewControllerRepresentable { func makeUIViewController( ... )
-> UIViewController { AppScreen( VStack { Text(“SwiftUI in Compose”) } ) } } SwiftUI in Compose Compose View
• Compose in SwiftUI • SwiftUI in Compose • UIKit
in Compose Interop
https: / / github.com/chrisbanes/tivi Tivi
Problem • Tivi App • Modal in Compose
Problem • Tivi App • Modal in Compose • Show
iOS date picker from Compose
Interop shared src commonMain androidMain iOSMain Expect Declaration
Interop shared src commonMain androidMain iOSMain Actual Declaration Actual Declaration
Interop @Composable expect fun TimePickerDialog( onDismissRequest: () -> Unit, onTimeChanged:
(LocalTime) -> Unit, selectedTime: LocalTime )
Interop shared src commonMain androidMain iOSMain Actual Declaration
Interop @Composable actual fun TimePickerDialog(…) { DatePickerViewController(backgroundColor).apply { ... confirmButton.setTitle(confirmLabel,
UIControlStateNormal) } }
Interop @Composable actual fun TimePickerDialog(…) { DatePickerViewController(backgroundColor).apply { ... confirmButton.setTitle(confirmLabel)
} }
Interop class DatePickerViewController( ... ) : UIViewController { }
Interop class DatePickerViewController( ... ) : UIViewController { val datePicker
= UIDatePicker() val stack = UIStackView() override fun viewDidLoad() { super.viewDidLoad() . .. view.addSubview(stack) } }
Interop class DatePickerViewController( ... ) : UIViewController { val datePicker
= UIDatePicker() val stack = UIStackView() override fun viewDidLoad() { super.viewDidLoad() . .. view.addSubview(stack) } }
Interop shared src commonMain androidMain iOSMain Actual Declaration
Interop @Composable actual fun TimePickerDialog(…) { }
Interop @Composable actual fun TimePickerDialog(…) { androidx.compose.material3.DatePickerDialog(…) { TimePicker( state
= timePickerState, modifier = Modifier .padding(top = 32.dp) .align(Alignment.CenterHorizontally), ) } }
Problem • Tivi App • Modal in Compose • Show
iOS date picker from Compose
• Compose in SwiftUI • SwiftUI in Compose • UIKit
in Compose Interop
None
Roadmap • Navigation • Transitions • Text selection and input
• Accessibility • Dialogs and popups
Building Shared UIs Across Platforms with Compose • Setup &
Architecture • Internals • Interop with iOS
Thank You! www.codingwithmohit.com @heyitsmohit