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
Mohit S
September 16, 2023
Programming
730
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Building Shared UIs across Platforms with Compose
Mohit S
September 16, 2023
More Decks by Mohit S
See All by Mohit S
Guide to Improving Compose Performance
heyitsmohit
0
340
Building Multiplatform Apps with Compose
heyitsmohit
2
630
Building StateFlows with Jetpack Compose
heyitsmohit
6
2k
Building Android Testing Infrastructure
heyitsmohit
1
650
Migrating to Kotlin State & Shared Flows
heyitsmohit
1
890
Using Square Workflow for Android & iOS
heyitsmohit
1
540
Building Android Infrastructure Teams at Scale
heyitsmohit
3
430
Strategies for Migrating to Jetpack Compose
heyitsmohit
2
680
Challenges of Building Kotlin Multiplatform Libraries
heyitsmohit
1
530
Other Decks in Programming
See All in Programming
仕様駆動開発の消費期限
watany
14
6.1k
React本体のコードリーディング
high_g_engineer
1
140
Apache Hive: そしてCloud Native Lakehouseへ
okumin
1
210
使いながら育てる Claude Code — 開発フローの1コマンド化 × 繰り返し指摘の自動仕組み化
shiki_kakaku
0
1.7k
freee が目指す データ マネジメント戦略 AI-Ready 時代を支える 攻めのガバナンスとは
freee
PRO
0
280
Laravel Boostに学ぶ、AIにPHPを書かせる技術 〜OSSの実装から蒸留するエージェント制御の王道〜
kentaroutakeda
3
670
自動化したのに回らないテスト運用の壁ーAI時代の品質責任と生産性
mfunaki
0
130
yield再入門 #phpcon
o0h
PRO
0
960
Apache Hive: Toward a Cloud Native Lakehouse
okumin
0
180
変わらないものが、変わるものを決める — 意図駆動開発 × イベントソーシング × イミュータブル | What Doesn't Change Decides What Can — IDD × Event Sourcing × Immutability
tomohisa
0
1.3k
数百円から始めるRuby電子工作
tarosay
0
140
Laravelで学ぶ Webアプリケーションチューニング入門/web_application_tuning_101
hanhan1978
4
1.7k
Featured
See All Featured
How to Ace a Technical Interview
jacobian
281
24k
Designing Powerful Visuals for Engaging Learning
tmiket
1
470
Speed Design
sergeychernyshev
33
2k
GitHub's CSS Performance
jonrohan
1033
470k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1.1k
Raft: Consensus for Rubyists
vanstee
141
7.6k
How to Talk to Developers About Accessibility
jct
2
490
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
1
230
The Illustrated Children's Guide to Kubernetes
chrisshort
51
53k
Un-Boring Meetings
codingconduct
0
370
Utilizing Notion as your number one productivity tool
mfonobong
4
510
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
3
410
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