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
1
700
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
310
Building Multiplatform Apps with Compose
heyitsmohit
2
590
Building StateFlows with Jetpack Compose
heyitsmohit
6
2k
Building Android Testing Infrastructure
heyitsmohit
1
590
Migrating to Kotlin State & Shared Flows
heyitsmohit
1
850
Using Square Workflow for Android & iOS
heyitsmohit
1
490
Building Android Infrastructure Teams at Scale
heyitsmohit
3
390
Strategies for Migrating to Jetpack Compose
heyitsmohit
2
630
Challenges of Building Kotlin Multiplatform Libraries
heyitsmohit
1
500
Other Decks in Programming
See All in Programming
テレメトリーシグナルが導くパフォーマンス最適化 / Performance Optimization Driven by Telemetry Signals
seike460
PRO
2
170
Understanding Apache Lucene - More than just full-text search
spinscale
0
140
安いハードウェアでVulkan
fadis
1
810
Rで始めるML・LLM活用入門
wakamatsu_takumu
0
210
「効かない!」依存性注入(DI)を活用したAPI Platformのエラーハンドリング奮闘記
mkmk884
0
250
Nuxt Server Components
wattanx
0
130
Java 21/25 Virtual Threads 소개
debop
0
280
PHP 7.4でもOpenTelemetryゼロコード計装がしたい! / PHPerKaigi 2026
arthur1
1
420
仕様漏れ実装漏れをなくすトレーサビリティAI基盤のご紹介
orgachem
PRO
7
3.1k
Codex CLIのSubagentsによる並列API実装 / Parallel API Implementation with Codex CLI Subagents
takatty
2
500
へんな働き方
yusukebe
6
2.8k
RailsのValidatesをSwift Macrosで再現してみた
hokuron
0
130
Featured
See All Featured
How to build a perfect <img>
jonoalderson
1
5.3k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
250
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.7k
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
64
52k
sira's awesome portfolio website redesign presentation
elsirapls
0
200
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
140
Bootstrapping a Software Product
garrettdimon
PRO
307
120k
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
390
Tell your own story through comics
letsgokoyo
1
870
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
2
3.7k
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
220
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
0
470
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